﻿/*
	This file is part of Limpet.

    Limpet is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Limpet is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with Limpet.  If not, see http://www.gnu.org/licenses/.
	
	Copyright 2010 Dan Popick
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Text;
using System.Web;
using System.Web.Caching;
using System.Web.UI;
using System.Xml;

namespace Open.Web.Diagnostics.Limpet.Modules
{
    /// <summary>
    /// A Limpet module which examines all currently loaded assemblies and reports
    /// whether they were compiled with optimizations
    /// </summary>
    public class BuildCheckModule: ISelfRenderingLimpetModule
    {
        // helper class -- tracks status for a single DLL
        class CompileState
        {
            public string AssemblyName;
            public bool IsTrackingEnabled;
            public bool IsOptimizedDisabled;

            public CompileState(string name, bool trackingEnabled, bool optimizedDisabled)
            {
                AssemblyName = name;
                IsTrackingEnabled = trackingEnabled;
                IsOptimizedDisabled = optimizedDisabled;
            }
        }

        // A cached list of DLL statuses
        static List<CompileState> AssemblyStates
        {
            get
            {
                // stored in the HTTP cache for 5 minutes
                HttpContext ctx = HttpContext.Current;
                List<CompileState> list = ctx.Cache.Get("__limpet_bcm_list") as List<CompileState>;
                if (list == null)
                {
                    ctx.Cache.Add("__limpet_bcm_list",
                        list = BuildCompileList(),
                        null,
                        DateTime.Now.AddMinutes(5),
                        TimeSpan.Zero,
                        CacheItemPriority.Normal,
                        null);
                }

                return list;
            }
        }

        // Builds a list of all currently-loaded assemblies 
        static List<CompileState> BuildCompileList()
        {
            List<CompileState> list = new List<CompileState>();
            foreach (Assembly a in AppDomain.CurrentDomain.GetAssemblies())
            {
                // load attributes for each assembly
                bool trackingOn = false, optOn = false;
                foreach (Attribute attribute in a.GetCustomAttributes(false))
                {
                    DebuggableAttribute da = attribute as DebuggableAttribute;
                    if (da == null)
                        continue;

                    trackingOn = da.IsJITTrackingEnabled;
                    optOn = da.IsJITOptimizerDisabled;
                    break;
                }

                list.Add(new CompileState(a.FullName, trackingOn, optOn));
            }

            // sort by name
            list.Sort((x, y) => x.AssemblyName.CompareTo(y.AssemblyName));
            return list;
        }

        public bool ShowDebugAsWarning { get; set; }

        public BuildCheckModule(string name, XmlNode configurationNode)
        {
            ShowDebugAsWarning = true;
            Name = name;
        }

        #region ISelfRenderingLimpetModule Members

        /// <summary>
        /// Renders the HTML details for this module
        /// </summary>
        /// <param name="stream">A stream onto which the details are written</param>
        public void RenderDetails(Stream stream)
        {
            TextWriter tw = new StreamWriter(stream);
            HtmlTextWriter writer = new HtmlTextWriter(tw);

            writer.BeginRender();
            writer.RenderBeginTag(HtmlTextWriterTag.H2);
            writer.WriteEncodedText("Assemblies");
            writer.RenderEndTag();

            writer.RenderBeginTag(HtmlTextWriterTag.Table);
            writer.RenderBeginTag(HtmlTextWriterTag.Tr);
                writer.RenderBeginTag(HtmlTextWriterTag.Th);
                writer.WriteEncodedText("Assembly");
                writer.RenderEndTag();
                writer.RenderBeginTag(HtmlTextWriterTag.Th);
                writer.WriteEncodedText("Tracking Enabled");
                writer.RenderEndTag();
                writer.RenderBeginTag(HtmlTextWriterTag.Th);
                writer.WriteEncodedText("Optimizations Disabled");
                writer.RenderEndTag();
            writer.RenderEndTag();

            foreach (CompileState cs in AssemblyStates)
            {
                if (cs.IsTrackingEnabled || cs.IsOptimizedDisabled)
                    writer.AddAttribute(HtmlTextWriterAttribute.Class, "highlight");

                writer.RenderBeginTag(HtmlTextWriterTag.Tr);
                writer.RenderBeginTag(HtmlTextWriterTag.Td);
                writer.WriteEncodedText(cs.AssemblyName);
                writer.RenderEndTag();
                writer.RenderBeginTag(HtmlTextWriterTag.Td);
                writer.WriteEncodedText(cs.IsOptimizedDisabled.ToString());
                writer.RenderEndTag(); 
                writer.RenderBeginTag(HtmlTextWriterTag.Td);
                writer.WriteEncodedText(cs.IsTrackingEnabled.ToString());
                writer.RenderEndTag();
                writer.RenderEndTag();
            }
            writer.RenderEndTag();

            writer.Flush();
        }

        #endregion

        #region ILimpetModule Members

        /// <summary>
        /// The name, as defined in the config file
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// Gets a single-line HTML message to display in the Limpet popup.
        /// </summary>
        /// <param name="context">The current HTTP context</param>
        /// <returns>A string of HTML to display in the popup.</returns>
        public SummaryMessage GetMessage(System.Web.HttpContext context)
        {
            string url = LimpetManager.GetSelfRenderingUrl(Name);

            foreach (CompileState cs in AssemblyStates)
                if (cs.IsTrackingEnabled)
                {
                    return new SummaryMessage()
                    {
                        Category = "Build",
                        Severity = ShowDebugAsWarning ? LimpetSeverity.Warning : LimpetSeverity.Info,
                        Text = "Some assemblies compiled in debug mode.",
                        Url = url
                    };
                }

            return new SummaryMessage()
            {
                Category = "Build",
                Severity = LimpetSeverity.Info,
                Text = "All assemblies compiled in release mode.",
                Url = url
            };
        }

        #endregion
    }
}
